#include "VideoEncoder.h"

VideoEncoder::VideoEncoder()
{ //paar dingen initialiseren
	videopts = 0;
	audiopts = 0;
	gEncVideoConvertFrame = avcodec_alloc_frame();
	gEncVideoConvertCtx = NULL;
	gEncAudioFifoOutBuf = NULL;

}

void VideoEncoder::EnableStreams(bool p_VideoEnabled, bool p_AudioEnabled)
{
	VideoEnabled = p_VideoEnabled;
	AudioEnabled = p_AudioEnabled;
}

void VideoEncoder::SetupEncoder(char *filename, CodecID videoCodec, CodecID audioCodec)
{ // encoder al een beetje invullen (de codec guessen, en een nieuwe videostream maken
	//Hier moet nog audio enz bijkomen ^^
	name = filename;
	if ((fmt = guess_format( NULL, filename, NULL )) == NULL)
		throw("VideoEncoder: Could not guess format codec\n");
	if ((out_format_context = av_alloc_format_context()) == NULL)
		throw("VideoEncoder: Can't allocate output format context.\n");

	if(fmt->audio_codec == CODEC_ID_AAC){
		fmt->audio_codec = CODEC_ID_AC3;
	}
	out_format_context->oformat = fmt;
	if(VideoEnabled)
	{
		if ((video_stream = av_new_stream(out_format_context, 0)) == NULL)
			throw("VideoEncoder:Can't add video stream to output file.\n");
		video_codec_context = video_stream->codec;
		//video_codec_context->codec_id = fmt->video_codec;
		video_codec_context->codec_id = videoCodec;
		video_codec_context->codec_type = CODEC_TYPE_VIDEO;
	}
	if(AudioEnabled)
	{
		if ((audio_stream = av_new_stream(out_format_context, 0)) == NULL)
			throw("VideoEncoder:Can't add audio stream to output file.\n");
		audio_codec_context = audio_stream->codec;
		//audio_codec_context->codec_id = fmt->audio_codec;
		audio_codec_context->codec_id = audioCodec;
		audio_codec_context->codec_type = CODEC_TYPE_AUDIO;
	}

}

void VideoEncoder::EncodeVideo(AVFrame *frame)
{ //the big stuff.. dit is enkel video, audio is andere functie...
	frame->pts = videopts; //stel u PTS in
	printf("Current Frame: %d\r", frame->pts); //print dat uit
	sws_scale(gEncVideoConvertCtx, frame->data, frame->linesize, INPUT_WIDTH, INPUT_HEIGHT, gEncVideoConvertFrame->data, gEncVideoConvertFrame->linesize); //dit is dus u Frame scalen naar nieuwe resolutie
	gEncVideoConvertFrame->pts = videopts; //nog een pts instelle :)

	int len = avcodec_encode_video ( video_codec_context, video_outbuf, video_outbuf_size, gEncVideoConvertFrame ); //Go encoder Go! data kopiere in video_outbuf
	if ( len >= 0 ) 
	{
		// If we got some data, multiplex it
		AVPacket out_packet; //packet openen voor het schrijven
		av_init_packet( &out_packet ); //initialiseren
		out_packet.pts = av_rescale_q(videopts, video_codec_context->time_base, video_stream->time_base); //u pts rescalen ten opzichte van de output timebase..., anders kloppe u filmkes niet zoals eerst
		if ( video_codec_context->coded_frame->key_frame )
			out_packet.flags |= PKT_FLAG_KEY;
		out_packet.stream_index = video_stream->index; //stream index vast zetten
		out_packet.data = video_outbuf; //data kopieren
		out_packet.size = len; //lengte kopieren
		av_write_frame( out_format_context, &out_packet ); //en nu uitschrijven naar file :)
		videopts++;
	}
}

void VideoEncoder::EncodeAudio( int16_t* samples, int buffersize)
{ //the big stuff.. dit is enkel video, audio is andere functie...


		int		nBytesToWrite = 0;
		uint8_t *	pRawSamples = NULL;
		int		nAudioFrameSizeOut = audio_codec_context->frame_size * audio_codec_context->channels * sizeof(int16_t);
		nBytesToWrite = buffersize;
		pRawSamples  = (uint8_t*)samples;
		av_fifo_write(&gEncAudioFifo, pRawSamples, nBytesToWrite);
		while (av_fifo_read(&gEncAudioFifo, gEncAudioFifoOutBuf, nAudioFrameSizeOut) == 0)
		{
			int len = avcodec_encode_audio ( audio_codec_context, audio_outbuf, audio_outbuf_size, (int16_t*)gEncAudioFifoOutBuf ); //Go encoder Go! data kopiere in audio_outbuf
			if ( len >= 0 ) 
			{
				// If we got some data, multiplex it
				AVPacket out_packet; //packet openen voor het schrijven
				av_init_packet( &out_packet ); //initialiseren
				out_packet.flags |= PKT_FLAG_KEY;
				//out_packet.pts = av_rescale_q(audiopts, audio_codec_context->time_base, audio_stream->time_base); //u pts rescalen ten opzichte van de output timebase..., anders kloppe u filmkes niet zoals eerst
				out_packet.stream_index = audio_stream->index; //stream index vast zetten
				out_packet.data = audio_outbuf; //data kopieren
				out_packet.size = len; //lengte kopieren
				av_write_frame( out_format_context, &out_packet ); //en nu uitschrijven naar file :)
			}
		}
}
void VideoEncoder::OpenOutput()
{ //open van output file
	SearchCodec(); //zoeken naar codec
	url_fopen( &out_format_context->pb, name, URL_WRONLY ); //file openen

	if(av_write_header( out_format_context ) != 0)
		throw("Error Writing Header"); //header schrijven
}
void VideoEncoder::CloseOutput()
{ //fileke sluiten
	if(av_write_trailer( out_format_context ) != 0) //eerst trailer schrijven
		throw("Error Writing Trailer");
	url_fclose( &out_format_context->pb ); //sluiten van file
}

void VideoEncoder::SearchCodec()
{ //codec zoeken van de outputfile & openen, hier moet ook nog audio bij denk ik
	if(VideoEnabled)
	{
		video_codec = avcodec_find_encoder ( video_codec_context->codec_id );
		if(video_codec==NULL)
		{
			throw("Unsupported Output Video Codec\n");
		}
		else if(avcodec_open ( video_codec_context, video_codec ) < 0)
			{
				throw("Could not open Output Video Codec\n");
			}
		avcodec_open ( video_codec_context, video_codec );
		video_outbuf_size = 400000; //video bufferke aanmake
		video_outbuf = (uint8_t*) av_malloc( video_outbuf_size );
	}
	if(AudioEnabled)
	{
		audio_codec = avcodec_find_encoder ( audio_codec_context->codec_id );
		if(audio_codec==NULL)
		{
			throw("Unsupported Output Audio Codec\n");
		}
		else if(avcodec_open ( audio_codec_context, audio_codec ) < 0)
			{
				throw("Could not open Ouput Audio Codec\n");
			}
		audio_outbuf_size = 400000;
		audio_outbuf = (uint8_t*) av_malloc( audio_outbuf_size );
		
		// The encoder may require a minimum number of raw audio samples for each encoding but we can't
		// guarantee we'll get this minimum each time an audio frame is decoded from the input file so 
		// we use a FIFO to store up incoming raw samples until we have enough for one call to the codec.
		av_fifo_init(&gEncAudioFifo, 2 * AVCODEC_MAX_AUDIO_FRAME_SIZE);
		// Allocate a buffer to read OUT of the FIFO into. The FIFO maintains its own buffer internally.
		if ((gEncAudioFifoOutBuf = (uint8_t*)av_malloc(2 * AVCODEC_MAX_AUDIO_FRAME_SIZE)) == NULL)
		{
			throw("MUX: ERROR - Can't allocate buffer to read into from audio FIFO.\n");
		}
	}

}

void VideoEncoder::FillParameters(int bitrate, int den, int num, int gopsize, int width, int height, int sample_rate, int audio_bitrate, int channels)
{ //paramaters voor de Encoder invullen, hier moet ook audio nog bij
	if(VideoEnabled)
	{
		video_codec_context->bit_rate = bitrate;
		video_codec_context->time_base.den = den;
		video_codec_context->time_base.num = num;
		video_codec_context->gop_size = gopsize;
		video_codec_context->pix_fmt = PIX_FMT_YUV420P;
		video_codec_context->width = width;
		video_codec_context->height = height;
	}
	if(AudioEnabled)
	{
		audio_codec_context->time_base.den = audio_bitrate;
		audio_codec_context->time_base.num = 1;
		audio_codec_context->bit_rate = audio_bitrate;
		audio_codec_context->sample_rate = sample_rate;
		audio_codec_context->channels = channels;
	}

	av_set_parameters(out_format_context, NULL);
}

void VideoEncoder::SetConvertParameters(AVCodecContext* codec_context)
{ //de Scaler instellen

	if(VideoEnabled)
	{
		gEncVideoConvertCtx = sws_getContext(codec_context->width,codec_context->height, codec_context->pix_fmt, video_codec_context->width, video_codec_context->height, video_codec_context->pix_fmt, SWS_FAST_BILINEAR, NULL, NULL, NULL);		
		INPUT_HEIGHT = codec_context->height;
		INPUT_WIDTH = codec_context->width;

		// Determine required buffer size and allocate buffer
		int numBytes=avpicture_get_size(PIX_FMT_YUV420P, video_codec_context->width, video_codec_context->height);
		uint8_t *buffer=(uint8_t *)av_malloc(numBytes*sizeof(uint8_t));
  
		  // Assign appropriate parts of buffer to image planes in pFrameRGB
		  // Note that pFrameRGB is an AVFrame, but AVFrame is a superset
		  // of AVPicture
		  avpicture_fill((AVPicture *)gEncVideoConvertFrame, buffer, PIX_FMT_YUV420P, video_codec_context->width, video_codec_context->height);
	}
	
}

void VideoEncoder::SetTotalPTS(int pts)
{ //Niet gebruikte functie;;;
	//totalPTS += pts;
}